package com.hyt.it.ogt.ks.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.it.ogt.ks.config.KsBizRunTimeException;
import com.hyt.it.ogt.ks.enums.BizStatusCode;
import com.hyt.it.ogt.ks.enums.CandidateStatusCode;
import com.hyt.it.ogt.ks.enums.ExamConfigCode;
import com.hyt.it.ogt.ks.feign.KwClient;
import com.hyt.it.ogt.ks.feign.model.Exam;
import com.hyt.it.ogt.ks.feign.model.ExamCandidate;
import com.hyt.it.ogt.ks.feign.model.ExamDTO;
import com.hyt.it.ogt.ks.job.CandidateProcessAccessTimeTask;
import com.hyt.it.ogt.ks.model.entity.CandidateProcess;
import com.hyt.it.ogt.ks.model.vo.CandidateInfoVO;
import com.hyt.it.ogt.ks.model.vo.CandidateLoginRequestVO;
import com.hyt.it.ogt.ks.model.vo.ExamConfigVO;
import com.hyt.it.ogt.ks.model.vo.login.LoginInfo;
import com.hyt.it.ogt.ks.mq.model.CandidateInfoUpdateDTO;
import com.hyt.it.ogt.ks.service.ExamInfoCacheService;
import com.hyt.it.ogt.ks.service.ICandidateProcessService;
import com.hyt.it.ogt.ks.service.IExamCacheService;
import com.hyt.it.ogt.ks.service.ILoginService;
import com.hyt.it.ogt.ks.util.AdmissionNumberEncryptUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;


@Slf4j
@Service
public class LoginServiceImpl implements ILoginService {
    @Resource
    private ICandidateProcessService iCandidateProcessService;
    @Resource
    private CandidateProcessAccessTimeTask candidateProcessAccessTimeTask;
    @Resource
    private IExamCacheService iExamCacheService;
    @Resource
    private ExamInfoCacheService examInfoCacheService;
    @Resource
    private KwClient kwClient;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public CandidateInfoVO login(CandidateLoginRequestVO requestVO, HttpServletRequest request) {
        // 定义返回值对象
        CandidateInfoVO candidateInfoVO = CandidateInfoVO.builder().build();
        // 获取考试配置
        List<ExamConfigVO> configList = iExamCacheService.getExamConfigCache(requestVO.getExamId());
        // 登录校验
        this.check(requestVO, candidateInfoVO, configList);

        if (candidateInfoVO.getExamState() == 1) {
            CandidateProcess candidateProcess = this.getCandidateProcess(requestVO, configList);
            // 允许登录
            candidateInfoVO.setLoginState(0);
            CandidateProcess entity = new CandidateProcess();
            entity.setExamStatus(CandidateStatusCode.LOGIN.getCode());
            entity.setDeviceType(requestVO.getDeviceType());
            // 查询是否已经登录过
            if (StringUtils.isBlank(candidateProcess.getId())) {
                // 登录成功后考生过程表插入一条数据, 考生多处登录或并发情况下，可能会出现重复插入数据情况
                entity.setCandidateId(candidateInfoVO.getCandidateId());
                entity.setExamId(requestVO.getExamId());
                entity.setAdmissionNumber(candidateInfoVO.getAdmissionNumber());
                entity.setLoginCount(1);
                //准考证加密
                String psn = String.valueOf(AdmissionNumberEncryptUtil.encrypt(candidateInfoVO.getAdmissionNumber(), candidateInfoVO.getCandidateId()));
                entity.setPasswordNumber(psn);
                candidateInfoVO.setPasswordNumber(entity.getPasswordNumber());
                try {
                    iCandidateProcessService.save(entity);
                } catch (DuplicateKeyException e) {
                    log.error("考生登录联合主键冲突", e);
                }
            } else {
                entity.setId(candidateProcess.getId());
                entity.setLoginCount(candidateProcess.getLoginCount() + 1);
                // 修改为已登录状态
                iCandidateProcessService.updateById(entity);
                //准考证加密
                candidateInfoVO.setPasswordNumber(candidateProcess.getPasswordNumber());
            }
            // 通知考务
            iCandidateProcessService.updateKwCandidateInfo(CandidateInfoUpdateDTO.builder().examId(requestVO.getExamId())
                    .candidateId(candidateInfoVO.getCandidateId()).state(CandidateStatusCode.LOGIN.getCode())
                    .lateAllowLogin(false).build());
            // 记录日志

            // 清空accessTime字段，为了剩余时长重新计算
            candidateProcessAccessTimeTask.loginAsyncDealEvent(requestVO, candidateInfoVO.getCandidateId());
            candidateInfoVO.setAdmissionNumber(candidateProcess.getAdmissionNumber());
            candidateInfoVO.setCandidateId(candidateProcess.getId());
            candidateInfoVO.setExamId(candidateProcess.getExamId());
            candidateInfoVO.setForbidEndExam(candidateProcess.getForbidFinishExam());
            candidateInfoVO.setState(candidateProcess.getExamStatus());
        }
        return candidateInfoVO;
    }

    public CandidateProcess getCandidateProcess(CandidateLoginRequestVO requestVO, List<ExamConfigVO> configList) {
        CandidateProcess cp = new CandidateProcess();
        LambdaQueryWrapper<CandidateProcess> query = Wrappers.<CandidateProcess>lambdaQuery().select(CandidateProcess::getExamStatus,
                        CandidateProcess::getLoginCount, CandidateProcess::getPasswordNumber, CandidateProcess::getId)
                .eq(CandidateProcess::getExamId, requestVO.getExamId())
                .eq(CandidateProcess::getDelFlag, false)
                .last("limit 1");
        if ("3".equals(requestVO.getLoginType())) {
            query.eq(CandidateProcess::getAdmissionNumber, requestVO.getAdmissionNumber());
            cp.setAdmissionNumber(requestVO.getAdmissionNumber());
        } else {
            ApiResponse<LoginInfo> res = kwClient.getCandidateAdmissionNumber(requestVO.getExamId(), requestVO.getAdmissionNumber(), requestVO.getLoginType());
            if (res.getCode().intValue() != BizStatusCode.KW_SUCCESS_OK.getCode().intValue()) {
                throw new KsBizRunTimeException(res.getCode());
            }
            String admissionNumber = res.getData().getAdmissionNumber();
            cp.setCandidateId(res.getData().getCandidateId());
            cp.setAdmissionNumber(admissionNumber);
            // 准考证号唯一，应该是只有一条数据而已
            query.eq(CandidateProcess::getAdmissionNumber, admissionNumber);
        }
        CandidateProcess candidate = iCandidateProcessService.getOne(query);
        if (candidate != null) {
            // 如果已经考试完成，则不允许登陆
            if (candidate.getExamStatus().intValue() >= CandidateStatusCode.COMMIT.getCode().intValue()) {
                throw new KsBizRunTimeException(BizStatusCode.HAVE_FINISH_EXAM.getCode());
            }

            if (candidate.getLoginCount() != null) {
                Optional<ExamConfigVO> loginCountConfig = configList.stream().filter(l -> l.getConfigCode()
                        .equals(ExamConfigCode.EXAM_CONFIG_CODE_LOGIN_COUNT.getCode())).findFirst();
                if (loginCountConfig.isPresent()) {
                    int loginCountLimit = Integer.valueOf(loginCountConfig.get().getConfigValue());
                    if (loginCountLimit > 0 && candidate.getLoginCount() >= loginCountLimit) {
                        throw new KsBizRunTimeException(BizStatusCode.OUT_LOGIN_LIMIT.getCode());
                    }
                }
            }

            cp.setLoginCount(candidate.getLoginCount());
            cp.setId(candidate.getId());
            cp.setPasswordNumber(candidate.getPasswordNumber());
        } else {
            // 获取考生信息内容
            String resp = kwClient.getExamCandidateInfo(requestVO.getExamId(), cp.getAdmissionNumber());
            log.debug("调用考务系统获取考生信息：{}", resp);
            ApiResponse<ExamCandidate> clientResponse = ApiResponse.ConvertRet(resp, ExamCandidate.class);
            // 判断考务是否有考生
            if (clientResponse.getCode().intValue() != BizStatusCode.KW_SUCCESS_OK.getCode().intValue()) {
                throw new KsBizRunTimeException(clientResponse.getCode(), "");
            } else if (clientResponse.getData().getState() >= CandidateStatusCode.COMMIT.getCode().intValue()) {
                throw new KsBizRunTimeException(BizStatusCode.ABSENT_LOGIN_FAIL.getCode());
            }
        }
        return cp;
    }

    private void check(CandidateLoginRequestVO requestVO, CandidateInfoVO candidateInfoVO, List<ExamConfigVO> configList) {
        String examId = requestVO.getExamId();
        String code = requestVO.getAdmissionNumber();
        if (StringUtils.isBlank(examId) || StringUtils.isBlank(code) || StringUtils.isBlank(requestVO.getLoginType())) {
            throw new KsBizRunTimeException(BizStatusCode.KW_INVALID_PARAMETER.getCode());
        }
        Boolean idCardLogin = configList.stream().filter(ecl ->
                ExamConfigCode.EXAM_CONFIG_CODE_ID_CARD_LOGIN.getCode().equals(ecl.getConfigCode())
                        && "true".equals(ecl.getConfigValue())).count() > 0;
        Boolean phoneLogin = configList.stream().filter(ecl ->
                ExamConfigCode.EXAM_CONFIG_CODE_PHONE_NUM_LOGIN.getCode().equals(ecl.getConfigCode())
                        && "true".equals(ecl.getConfigValue())).count() > 0;
        if(!idCardLogin && "1".equals(requestVO.getLoginType())) {
            // 未开启身份证号登录
            throw new KsBizRunTimeException(BizStatusCode.LOGIN_NOT_BY_ID_CARD.getCode());
        } else if(!phoneLogin && "2".equals(requestVO.getLoginType())) {
            // 未开启手机号登录
            throw new KsBizRunTimeException(BizStatusCode.LOGIN_NOT_BY_PHONE.getCode());
        }

        Integer examState = 0;
        // 提前登陆分钟数
        Integer earlyLogOnMinutes = 0;
        // 迟到分钟数后不允许登陆
        Integer delayLogOnMinutes = 0;

        Boolean videoMonitoring = false;
        // 获取考试信息
        ExamDTO examDTO = examInfoCacheService.getExamInfoFromCache(examId);
        if (Objects.nonNull(examDTO)) {
            Exam exam = examDTO.getExam();
            // 未发布考试，不允许登录
            if (!exam.getPublishExam()) {
                throw new KsBizRunTimeException(BizStatusCode.EXAM_NO_PUBLISH_FAIL.getCode(),
                        BizStatusCode.EXAM_NO_PUBLISH_FAIL.getMsg());
            }
            LocalDateTime now = LocalDateTime.now();
            Optional<ExamConfigVO> earlyLogOnConfig = configList.stream().filter(l -> l.getConfigCode()
                    .equals(ExamConfigCode.EXAM_CONFIG_EARLY_LOG_ON.getCode())).findFirst();
            Optional<ExamConfigVO> earlyLogOnMinsConfig = configList.stream().filter(l -> l.getConfigCode()
                    .equals(ExamConfigCode.EXAM_CONFIG_EARLY_LOG_ON_MINS.getCode())).findFirst();
            Optional<ExamConfigVO> delayLogOnConfig = configList.stream().filter(l -> l.getConfigCode()
                    .equals(ExamConfigCode.EXAM_CONFIG_DELAY_LOG_ON.getCode())).findFirst();
            Optional<ExamConfigVO> delayLogOnMinsConfig = configList.stream().filter(l -> l.getConfigCode()
                    .equals(ExamConfigCode.EXAM_CONFIG_DELAY_LOG_ON_MINS.getCode())).findFirst();
            Optional<ExamConfigVO> videoMonitoringConfig = configList.stream().filter(l -> l.getConfigCode()
                    .equals(ExamConfigCode.EXAM_CONFIG_VIDEO_MONITORING.getCode())).findFirst();

            Boolean earlyLogOn = earlyLogOnConfig.isPresent() ? earlyLogOnConfig.get().getConfigValue().equals("true") : false;
            earlyLogOnMinutes = earlyLogOnMinsConfig.isPresent() ? Integer.parseInt(earlyLogOnMinsConfig.get().getConfigValue()) : 0;
            Boolean delayLogOn = delayLogOnConfig.isPresent() ? delayLogOnConfig.get().getConfigValue().equals("true") : false;
            delayLogOnMinutes = delayLogOnMinsConfig.isPresent() ? Integer.parseInt(delayLogOnMinsConfig.get().getConfigValue()) : 0;
            videoMonitoring = videoMonitoringConfig.isPresent() ? videoMonitoringConfig.get().getConfigValue().equals("true") : false;

            // 已过考试时间
            if (exam.getEndDate().isBefore(now)) {
                examState = 2;
            } else if (!earlyLogOn && exam.getBeginDate().isAfter(now)) {
                // 没有设置提前开考时间，则直接判断开始时间是否到
                examState = 0;
            } else {
                examState = 1;
                if (earlyLogOn) {
                    // 考试开始时间+提前开考分钟数>北京时间，不允许登陆
                    if (exam.getBeginDate().minusMinutes(earlyLogOnMinutes).isAfter(now)) {
                        examState = 3;
                    }
                }
                if (examState == 1 && delayLogOn) {
                    String lateAllowLoginInfo = kwClient.getLateAllowLoginInfo(examId, code);
                    ApiResponse<Boolean> lateAllowLoginInfoResponse = ApiResponse.ConvertRet(lateAllowLoginInfo, Boolean.class);
                    boolean flag = exam.getBeginDate().plusMinutes(delayLogOnMinutes).isAfter(now) ||
                            (lateAllowLoginInfoResponse.getData() != null && lateAllowLoginInfoResponse.getData());
                    if (!flag) {
                        examState = 4;
                    }
                }
            }
            candidateInfoVO.setDelayLogOnMins(delayLogOnMinutes);
            candidateInfoVO.setEarlyLogOnMins(earlyLogOnMinutes);
        }
        candidateInfoVO.setExamState(examState).setVideoMonitoring(videoMonitoring);
    }
}
